#!/usr/bin/env python
# -*- coding:utf8 -*-
# uncompyle6 version 2.15.1
# Python bytecode 2.7 (62211)
# Decompiled from: Python 2.7.10 (default, Jul  1 2017, 13:36:56) 
# [GCC 4.4.6 20110731 (Red Hat 4.4.6-4)]
# Embedded file name: ./esb/esb/outgoing.py
# Compiled at: 2017-11-16 15:44:28
"""
Tencent is pleased to support the open source community by making 蓝鲸智云(BlueKing) available.
Copyright (C) 2017 THL A29 Limited, a Tencent company. All rights reserved.
Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at http://opensource.org/licenses/MIT
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and limitations under the License.

All outgoing requests:
"""
import json, time, datetime, urlparse, socket
from urlparse import urljoin
import requests
from django.utils.encoding import smart_str
from requests.exceptions import ReadTimeout, SSLError
from django.conf import settings
from common.base_utils import FancyDict, datetime_format
from common.bkerrors import bk_error_codes
from common.errors import RequestThirdPartyException, TestHostNotFoundException, request_third_party_error_codes, RequestSSLException
from common.log import logger, logger_api
from .utils import SmartHost, get_ssl_root_dir
REQUEST_TIMEOUT_SECS = settings.REQUEST_TIMEOUT_SECS
STATUS_CODE_OK = 200
RESP_LIMIT_SIZE = 4096

class RequestsWrapper(object):
    """
    Wrapper for Requests
    """

    def request(self, *args, **kwargs):
        response_encoding = kwargs.pop('response_encoding', None)
        timeout = kwargs.get('timeout') or REQUEST_TIMEOUT_SECS
        kwargs.update(timeout=timeout, verify=False)
        resp = requests.request(*args, **kwargs)
        if response_encoding:
            resp.encoding = response_encoding
        return {'text': resp.text,
           'status_code': resp.status_code,
           'headers': resp.headers,
           'reason': resp.reason
           }


def get_current_http_wrapper():
    return RequestsWrapper()


def encode_dict(d, encoding='utf-8'):
    """
    使用指定的编码来编码给定的字典，否则使用urlencode方法的时候会报编码错误
    
    :param dict d: 需要转换编码的字典对象
    :param str encoding: 需要转换的目标编码
    """
    result = {}
    for k, v in d.iteritems():
        if isinstance(v, unicode):
            result[k] = v.encode(encoding)
        else:
            result[k] = v

    return result


class BasicHttpClient(object):
    """
    A very basic HTTP Client
    """

    @property
    def smart_http_client(self):
        return get_current_http_wrapper()

    def request(self, *args, **kwargs):
        """
        直接使用 _request 方法来发送请求
        """
        return self._request(*args, **kwargs)

    def request_by_url(self, method, url, *args, **kwargs):
        """
        使用一个完整的 url 来替代 host 和 path 参数
        """
        parsed_url = urlparse.urlparse(url)
        host = '%s://%s' % (parsed_url.scheme, parsed_url.netloc)
        return self.request(method, host, parsed_url.path, *args, **kwargs)

    def _request(self, method, host, path, params=None, data=None, headers={}, response_type='json', max_retries=0, response_encoding=None, request_encoding=None, use_test_env=False, verify=False, cert=None, timeout=None):
        """
        Send a request to given destination
        
        :param str method: One of "GET/POST/HEAD/DELETE"
        :param str host: host, such as "http://www.qq.com/"
        :param str path: request path, like "/account/login/"
        :param str/dict params: params in query string
        :param str/dict data: data to send in POST request
        :param str response_type: type of response, can be one of "json"
        :param int max_retries: 最多可以重试的次数，默认为0，不重试
        :param str response_encoding: 结果内容的编码，默认自动猜测
        :param str request_encoding: 请求参数编码
        :param str/bool verify: 是否校验crt
        :param string/tuple: 传递客户端crt和key
        :param int timtout: 超时时间
        :returns: response
        """
        url = self.make_url(host, path, use_test_env=use_test_env)
        request_exception = None
        resp, resp_status_code, resp_text = (None, -1, '')
        result = None
        params_to_send, data_to_send = params, data
        if request_encoding:
            if isinstance(params, dict):
                params_to_send = encode_dict(params, encoding=request_encoding)
            if isinstance(data, dict):
                data_to_send = encode_dict(data, encoding=request_encoding)
        try:
            client = self.smart_http_client
            logger.debug('Starting request to url=%s, params=%s, data=%s', url, params, data)
            resp = client.request(method, url, params=params_to_send, data=data_to_send, headers=headers, response_encoding=response_encoding, verify=verify, cert=cert, timeout=timeout)
            resp_text = resp['text']
            resp_status_code = resp['status_code']
            logger.debug('Response from url=%s, params=%s, data=%s, response=%s', url, params, data, resp_text)
            if resp_status_code != STATUS_CODE_OK:
                status_code = 'STATUS_CODE_%s' % resp_status_code
                raise Exception(u'状态码: %s,错误消息: %s' % (
                 resp_status_code,
                 request_third_party_error_codes.error_codes.get(status_code, u'第三方系统接口异常，%s' % resp['reason'])))
        except Exception as e:
            logger.exception('%s Error occured when sending request to %s', bk_error_codes.REQUEST_THIRD_PARTY_ERROR.code, url)
            if isinstance(e, ReadTimeout):
                request_exception = ReadTimeout(u'第三方系统接口响应时间超过%s秒' % (timeout or REQUEST_TIMEOUT_SECS))
            else:
                request_exception = e
            if max_retries > 0:
                seconds_wait = 1
                max_retries -= 1
                if isinstance(host, SmartHost):
                    logger.info('Shift request host for %s', url)
                    host.shift_host(use_test_env=use_test_env)
                logger.info('Will Retry request after %s seconds, remaining retries = %s', seconds_wait, max_retries)
                time.sleep(seconds_wait)
                return self._request(method, host, path, params, data, headers, response_type, max_retries, response_encoding, request_encoding, use_test_env)
        else:
            try:
                result = self.format_resp(resp_text, response_type=response_type)
            except Exception as e:
                logger.exception(u'%s resp_text: %s, response_type: %s', bk_error_codes.THIRD_PARTY_RESP_ERROR.code, resp_text, response_type)
                request_exception = e

        return FancyDict(url=url, resp=resp, resp_status_code=resp_status_code, resp_text=resp_text, result=result, request_exception=request_exception)

    def get(self, *args, **kwargs):
        return self.request('GET', *args, **kwargs)

    def post(self, *args, **kwargs):
        return self.request('POST', *args, **kwargs)

    def head(self, *args, **kwargs):
        return self.request('GET', *args, **kwargs)

    def delete(self, *args, **kwargs):
        return self.request('POST', *args, **kwargs)

    @staticmethod
    def make_url(host, path, use_test_env):
        if isinstance(host, SmartHost):
            if use_test_env and not host.has_test_host():
                raise TestHostNotFoundException(u'错误，系统没有提供测试系统地址')
            host = host.get_value(use_test_env=use_test_env)
        if not host.startswith('http'):
            host = 'http://%s' % host
        return urljoin(host, path)

    @staticmethod
    def format_resp(resp_text, encoding='utf-8', response_type='json'):
        """
        Format the given response
        """
        if response_type == 'json':
            resp = json.loads(resp_text)
        else:
            resp = resp_text
        return resp


class HttpClient(BasicHttpClient):
    """
    Send outgoing HTTP request
    """

    def __init__(self, component):
        self.component = component

    def request(self, method, host, path, params=None, data=None, headers={}, response_type='json', max_retries=0, response_encoding=None, request_encoding=None, verify=False, cert=None, timeout=None):
        """Send a request to given destination
        """
        datetime_start = datetime.datetime.now()
        if self.component.request:
            app_code = self.component.request.app_code
            use_test_env = self.component.request.use_test_env
            request_id = self.component.request.request_id
            timeout_time = self.component.request.timeout
        else:
            app_code = ''
            use_test_env = False
            request_id = None
            timeout_time = None
        system_name = self.component.sys_name
        component_name = self.component.get_component_name()
        if not timeout:
            if timeout_time:
                timeout = timeout_time if 1 else REQUEST_TIMEOUT_SECS
            r = self._request(method, host, path, params, data, headers, response_type, max_retries, response_encoding, request_encoding, use_test_env=use_test_env, verify=verify, cert=cert, timeout=timeout)
            if r.resp_status_code == 200 and not r.request_exception:
                response_to_log = r.resp_text[:RESP_LIMIT_SIZE]
            else:
                response_to_log = r.resp_text
            params = params or data
            params = isinstance(params, basestring) or json.dumps(params)
        datetime_end = datetime.datetime.now()
        msecs_cost = (datetime_end - datetime_start).total_seconds() * 1000
        exception_name = smart_str(r.request_exception) if r.request_exception else None
        try:
            api_log = {'message': 'Request outgoing finished, method=%s url=%s' % (method, r.url),
               'type': 'pyls-comp-api',
               'request_id': request_id,
               'req_app_code': app_code,
               'req_system_name': system_name,
               'req_component_name': component_name,
               'req_url': r.url,
               'req_params': params,
               'req_status': r.resp_status_code if r.resp else -1,
               'req_response': response_to_log,
               'req_exception': exception_name,
               'req_msecs_cost': int(msecs_cost),
               'req_start_time': datetime_format(datetime_start),
               'req_end_time': datetime_format(datetime_end)
               }
            logger_api.info(json.dumps(api_log))
        except Exception as e:
            logger.warning(u'logger api exception: %s' % e)

        if r.request_exception:
            if isinstance(r.request_exception, SSLError):
                r.request_exception.cert = cert
                r.request_exception.SSL_ROOT_DIR = get_ssl_root_dir()
                logger.error(u'%s request third party SSLError, system_name: %s, ssl_root_dir: %s', bk_error_codes.REQUEST_SSL_ERROR.code, system_name, r.request_exception.SSL_ROOT_DIR)
                raise RequestSSLException(r.request_exception, system_name=system_name, interface_name=self.component.get_component_name())
            else:
                raise RequestThirdPartyException(r.request_exception, system_name=system_name, interface_name=self.component.get_component_name())
        return r.result

    def request_by_url(self, method, url, *args, **kwargs):
        """
        使用一个完整的 url 来替代 host 和 path 参数
        """
        parsed_url = urlparse.urlparse(url)
        host = '%s://%s' % (parsed_url.scheme, parsed_url.netloc)
        path = '%s?%s' % (parsed_url.path, parsed_url.query) if parsed_url.query else parsed_url.path
        return self.request(method, host, path, *args, **kwargs)


class RequestHelperClient(BasicHttpClient):
    """Send outgoing request helper, Add Log"""

    def __init__(self, component):
        self.component = component

    def request(self, handler, action='', args=[], kwargs={}, timeout=None, api_name='', is_response_parse=True):
        datetime_start = datetime.datetime.now()
        if self.component and self.component.request:
            app_code = self.component.request.app_code
            timeout_time = self.component.request.timeout
            request_id = self.component.request.request_id
        else:
            app_code = None
            timeout_time = None
            request_id = None
        if self.component:
            system_name = self.component.sys_name
            component_name = api_name or self.component.get_component_name()
        else:
            system_name = ''
            component_name = ''
        if timeout or timeout_time:
            timeout = timeout_time if 1 else REQUEST_TIMEOUT_SECS
        request_url = ''
        request_exception = None
        request_params = {'action': action,'args': args,'kwargs': kwargs}
        resp_text = ''
        resp_status_code = -1
        result = None
        try:
            if action:
                resp = getattr(handler, action)(*args, **kwargs)
            else:
                resp = handler(*args, **kwargs)
        except Exception as e:
            logger.exception('%s error occured when request sys_name: %s, component_name: %s', bk_error_codes.REQUEST_THIRD_PARTY_ERROR.code, system_name, component_name)
            if isinstance(e, socket.timeout):
                request_exception = ReadTimeout(u'第三方系统接口响应超时，超过%s秒未返回数据' % timeout)
            else:
                request_exception = e
        else:
            if is_response_parse:
                request_url = resp.get('request_url', '')
                request_exception = resp.get('request_exception', None)
                request_params = resp.get('request_params') if hasattr(resp, 'request_params') else request_params
                resp_text = resp.get('resp_text', '')
                resp_status_code = resp.get('resp_status_code', -1)
                result = resp.get('result')
            else:
                resp_status_code = 200
                if isinstance(resp, basestring):
                    resp_text = resp
                else:
                    try:
                        resp_text = json.dumps(resp)
                    except:
                        resp_text = str(resp)

                result = resp

        if resp_status_code == 200 and not request_exception:
            response_to_log = resp_text[:RESP_LIMIT_SIZE]
        else:
            response_to_log = resp_text
        if not isinstance(request_params, basestring):
            try:
                request_params = json.dumps(request_params)
            except:
                request_params = str(request_params)

        datetime_end = datetime.datetime.now()
        msecs_cost = (datetime_end - datetime_start).total_seconds() * 1000
        exception_name = smart_str(request_exception) if request_exception else None
        try:
            api_log = {'message': 'Request outgoing finished, method=%s url=%s' % ('POST', request_url),
               'type': 'pyls-comp-api',
               'request_id': request_id,
               'req_app_code': app_code,
               'req_system_name': system_name,
               'req_component_name': component_name,
               'req_params': request_params,
               'req_status': resp_status_code,
               'req_response': response_to_log,
               'req_exception': exception_name,
               'req_msecs_cost': int(msecs_cost),
               'req_start_time': datetime_format(datetime_start),
               'req_end_time': datetime_format(datetime_end)
               }
            logger_api.info(json.dumps(api_log))
        except Exception as e:
            logger.warning(u'logger api exception: %s' % e)

        if request_exception:
            raise RequestThirdPartyException(request_exception, system_name=system_name, interface_name=self.component.get_component_name())
        return result
